package loadsave;

import java.io.*;
import java.util.*;

import loadsave.loadsaveobject.*;

public class Parser {
	//EOC is an EndOfCollection marker and EOO is an EndOfObject marker for the construction of LoadSaveComposites
	//  The memory references for the marker objects are more important than the actual values
	private static final LoadSaveObject EOC = new LoadSavePrimitive(";",";",";");
	private static final LoadSaveObject EOO = new LoadSavePrimitive(":",":",":");
	
	private static BufferedReader in;
	private static int lineNum = -1;
	private static String line = "";

	private static LoadSaveObject loadObject() throws IOException {
		try {
			if (!in.ready())
				return null;
			do {
				line = in.readLine().trim();
				++lineNum;
				//Check for comments
				//This first check looks to see if there is a comment at the end of the line (after any String
				//  literals) and ignores them if they exist (by removing them)
				if (line.contains("#") && (line.lastIndexOf('#') > line.lastIndexOf('\"')))
					line = line.substring(0, line.lastIndexOf('#'));
				//This next check looks to see if there is a comment before any String literals in the line and
				//  ignores them if they exist (by removing them)
				//Note: this check and removal for comments could invalidate the line, which is the intention if
				//  the comment is placed in the wrong place
				if (line.contains("#") && ((line.indexOf('#') < line.indexOf('\"')) || !line.contains("\"")))
					line = line.substring(0, line.indexOf('#'));
				line = line.trim();
			} while (in.ready() && line.equals(""));
		}
		catch (IOException e) {
			return null;
		}
		//If the end of a composite object is found, return the appropriate marker object
		if (line.matches("\\]"))
			return EOC;
		else if (line.matches("\\}"))
			return EOO;
		if (!line.matches(".*\\:.*\\:.*"))
			throw new IOException(lineNum + ": Invalid object declaration");
		String[] ids = line.split(":");
		if (ids.length < 3)
			throw new IOException(lineNum + ": Invalid object declaration - not enough identifiers");
		
		ids[0] = ids[0].trim();
		ids[1] = ids[1].trim();
		//If there were more than 2 colons in the object declaration, some of them may have been inside
		//  a String literal; concatenate all ids from the third onwards
		for (int i = 3; i < ids.length; ++i)
			ids[2] += ":" + ids[i];
		ids[2] = ids[2].trim();
		
		//If the third identifier (value) contains a colon and the colon is not inside quotes, the OML
		//  file is malformed; throw an exception
		if (ids[2].contains(":") && !ids[2].matches("\\s*\".*\"\\s*"))
			throw new IOException(lineNum + ": Invalid object declaration - misplaced colon");
		//If it is a primitive object, create and return a LoadSavePrimitive
		if (ids[2].matches("\".*\"")) {
			return new LoadSavePrimitive(ids[0], ids[1], ids[2].substring(1, ids[2].length() - 1));
		}
		//If it is a composite object, create and return a LoadSaveObjectComposite 
		else if (ids[2].matches("\\{")) {
			LoadSaveObjectComposite toReturn = new LoadSaveObjectComposite(ids[0], ids[1]);
			LoadSaveObject attribute;
			while ((attribute = loadObject()) != EOO) {
				//If the file ends before the object is closed, the file is malformed; throw an exception
				if (attribute == null)
					throw new IOException(lineNum + ": Unclosed object composite");
				//If the object is closed with the wrong bracket, the file is malformed; throw an exception 
				else if (attribute == EOC)
					throw new IOException(lineNum + ": Improper closing bracket for an object composite");
				else
					toReturn.add(attribute.getName(), attribute);
			}
			return toReturn;
		}
		//If it is a list/collection/grid, create and return a LoadSaveCollectionComposite
		else if (ids[2].matches("\\[\\s*<\\s*\\w+\\s*>")) {
			LoadSaveCollectionComposite toReturn = new LoadSaveCollectionComposite(ids[0], ids[1],
					ids[2].substring(ids[2].indexOf('<') + 1, ids[2].indexOf('>')).trim());
			LoadSaveObject index, entry, size = loadObject();
			if (!size.getName().equals("SIZE"))
				throw new IOException(lineNum + ": Missing size property in collection composite");
			toReturn.setSize((LoadSaveIndex) size); 
			while ((index = loadObject()) != EOC) {
				//If the file ends before the collection object is closed, the file is malformed; throw
				//  an exception
				if (index == null)
					throw new IOException(lineNum + ": Unclosed collection composite");
				//If the collection object is closed with the wrong bracket, the file is malformed; throw
				//  an exception 
				else if (index == EOO)
					throw new IOException(lineNum + ": Improper closing bracket for a collection composite");
				else if (!index.getType().equals("Index")) {
					throw new IOException(lineNum + ": Missing index in the collection composite");
				}
				else {
					entry = loadObject();
					//Run the same checks as before
					if (entry == null)
						throw new IOException(lineNum + ": Unclosed collection composite");
					else if (entry == EOO)
						throw new IOException(lineNum + ": Improper closing bracket for a collection composite");
					toReturn.add((LoadSaveIndex) index, entry);
				}
			}
			return toReturn;
		}
		//If it is a map, create and return a LoadSaveMapComposite
		else if (ids[2].matches("\\[\\s*<\\s*\\w+,\\s*\\w+\\s*>")) {
			String[] typeParams = ids[2].substring(ids[2].indexOf('<') + 1, ids[2].indexOf('>')).split("\\s*,\\s*");
			LoadSaveMapComposite toReturn = new LoadSaveMapComposite(ids[0], ids[1],
					typeParams[0].trim(), typeParams[1].trim());
			LoadSaveObject index, entry;
			while ((index = loadObject()) != EOC) {
				//If the file ends before the collection object is closed, the file is malformed; throw
				//  an exception
				if (index == null)
					throw new IOException(lineNum + ": Unclosed map composite");
				//If the collection object is closed with the wrong bracket, the file is malformed; throw
				//  an exception 
				else if (index == EOO)
					throw new IOException(lineNum + ": Improper closing bracket for a map composite");
				else {
					entry = loadObject();
					//Run the same checks as before
					if (entry == null)
						throw new IOException(lineNum + ": Unclosed collection composite");
					else if (entry == EOO)
						throw new IOException(lineNum + ": Improper closing bracket for a map composite");
					toReturn.add(index, entry);
				}
			}
			return toReturn;
		}
		//If it is an index for a list/collection/grid, create and return a LoadSaveIndex
		else if (ids[2].matches("(\\s*\\d+\\s*,\\s*)*(\\s*\\d+\\s*)")) {
			String[] indexStrs = ids[2].split("\\s*,\\s*");
			int[] indices = new int[indexStrs.length];
			try {
				for (int i = 0; i < indices.length; ++i)
					indices[i] = Integer.valueOf(indexStrs[i]);			
			}
			catch (NumberFormatException e) {
				throw new IOException(lineNum + ": Invalid index object");
			}
			return new LoadSaveIndex(ids[0], indices);
			
		}
		//If it is anything else, it's not a valid object; throw an exception
		else {
			//I could potentially do some additional parsing to determine the specific problem here, but
			//  I'll leave that for another time...
			throw new IOException(lineNum + ": Invalid object declaration");
		}
	}
	
	public static LoadSaveObject parse(String filename) throws FileNotFoundException {
		return Parser.parse(new BufferedReader(new FileReader(filename)));
	}
	
	public static LoadSaveObject parse(BufferedReader reader) {
		in = reader;
		lineNum = 0;
		line = "";
		LoadSaveObjectComposite toReturn = new LoadSaveObjectComposite("", "");
		LoadSaveObject lso;
		try {
			while ((lso = loadObject()) != null)
				toReturn.add(lso.getName(), lso);
		}
		catch (IOException e) {
			e.printStackTrace();
			return null;
		}
		return toReturn;
	}
}


class Main {

	static class Test implements loadsave.LoadSaveable {
		@Override
		public Memento getMemento() {
			return new Memento() {
				@Override
				public LoadSaveObject getState() {
					LoadSaveMapComposite lsmc = new LoadSaveMapComposite("ages", "HashMap", "String", "Integer");
					lsmc.add(new LoadSavePrimitive("", "", "Bob"), new LoadSavePrimitive("", "", "45"));
					lsmc.add(new LoadSavePrimitive("", "", "George"), new LoadSavePrimitive("", "", "98"));
					lsmc.add(new LoadSavePrimitive("", "", "Suzie"), new LoadSavePrimitive("", "", "8"));
					lsmc.add(new LoadSavePrimitive("", "", "Jim"), new LoadSavePrimitive("", "", "14"));
					lsmc.add(new LoadSavePrimitive("", "", "Dave"), new LoadSavePrimitive("", "", "48"));
					LoadSaveObjectComposite lsoc = new LoadSaveObjectComposite("stuph", "CoolObject");
					lsoc.add(lsmc.getName(), lsmc);
					lsoc.add("potato", new LoadSavePrimitive("potato", "double", "123.45"));
					LoadSaveCollectionComposite lscc = new LoadSaveCollectionComposite("something", "Year[][]", "Year");
					LoadSaveObjectComposite year = new LoadSaveObjectComposite("", "");
					year.add("year", new LoadSavePrimitive("year", "int", "1996"));
					lscc.add(new LoadSaveIndex(1, 2), year);
					year = new LoadSaveObjectComposite("", "SpecialYear");
					year.add("year", new LoadSavePrimitive("year", "int", "2000"));
					year.add("isLeapYear", new LoadSavePrimitive("isLeapYear", "boolean", "true"));
					lscc.add(new LoadSaveIndex(11, 2), year);
					year = new LoadSaveObjectComposite("", "");
					year.add("year", new LoadSavePrimitive("year", "int", "2199"));
					lscc.add(new LoadSaveIndex(0, 21), year);
					lsoc.add(lscc.getName(), lscc);
					return lsoc;
				}

				@Override
				public void restoreState(LoadSaveObject lso) {
				}
			};
		}
		
	}
	
	static class Potato implements loadsave.LoadSaveable {

		@Override
		public Memento getMemento() {
			return new Memento() {

				@Override
				public LoadSaveObject getState() {
					try {
						return Parser.parse("Load Save Data/OMLExample.oml");
					} catch (FileNotFoundException e) {
						e.printStackTrace();
						return null;
					}
				}

				@Override
				public void restoreState(LoadSaveObject lso) {
				}
				
			};
		}
		
	}
	
	public static void main(String[] args) {
		System.out.println(new Test().getMemento().getState().toString());
		Saver s = new loadsave.OMLSaver();
		s.save(new Test());
		s.save(new Potato());
	}
}